package cz.cuni.mff.ufal.httpserver.protocol;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLDecoder;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import cz.cuni.mff.ufal.httpserver.IncorrectHttpRequestException;
import cz.cuni.mff.ufal.volk.utils.MixedAsciiInputStream;

/**
 * An HTTP request.
 *
 * @author Bartłomiej Etenkowski
 */
public class HttpRequest {

  public HttpRequest(InputStream in) throws IncorrectHttpRequestException {
    this(in, "&");
  }

  public HttpRequest(InputStream in, String separator) throws IncorrectHttpRequestException {
    MixedAsciiInputStream cin = new MixedAsciiInputStream(new BufferedInputStream(in));
    inputStream = cin;

    try {
      // The first line of the request - method, resource and protocol description.
      String[] firstLine = cin.nextLine().split("[\\s]");
      if (firstLine.length != 3)
        throw new IncorrectHttpRequestException("The first line of the request does not contain three elements");
      method = HttpMethod.parse(firstLine[0]);
      if (method == null)
        throw new IncorrectHttpRequestException("Unknown request method");
      try {
        requestedUri = new URI(firstLine[1]);
      } catch (URISyntaxException e) {
        throw new IncorrectHttpRequestException("The URI has incorrect syntax", e);
      }
      if (separator != null) {
        try {
          parseQuery(queryParameters, requestedUri.getRawQuery(), separator);
        } catch (UnsupportedEncodingException e) {
          throw new IncorrectHttpRequestException("The encoding of the query is not supported", e);
        }
      }
      protocol = HttpProtocol.parse(firstLine[2]);
      if (protocol == null)
        throw new IncorrectHttpRequestException("Unsupported protocol");

      String line;
      while (!(line = cin.nextLine()).isEmpty()) {
        String[] keyValuePair = line.split(":[\\s]*", 2);
        if (keyValuePair.length != 2)
          throw new IncorrectHttpRequestException("Incorrect header line");
        headers.put(keyValuePair[0], keyValuePair[1]);
      }
    }
    catch (IOException e) {
      throw new IncorrectHttpRequestException("Cannot read the stream", e);
    }
  }

  private final HttpMethod method;

  public HttpMethod getMethod() {
    return method;
  }

  private final URI requestedUri;

  public URI getRequestedUri() {
    return requestedUri;
  }

  private final HttpProtocol protocol;

  /**
   * @return The protocol used in the request.
   */
  public HttpProtocol getProtocol() {
    return protocol;
  }

  private final Map<String, String> queryParameters = new HashMap<String, String>();

  /** The wrapper for the {@link #queryParameters} map which enables read-only external access. */
  private Map<String, String> unmodifiableQueryParameters = Collections.unmodifiableMap(queryParameters);

  public Map<String, String> getQueryParameters() {
    return unmodifiableQueryParameters;
  }

  private final Map<String, String> postParameters = new HashMap<String, String>();

  private final Map<String, String> unmodifiablePostParameters = Collections.unmodifiableMap(postParameters);

  public Map<String, String> getPostParameters() {
    return unmodifiablePostParameters;
  }

  private final InputStream inputStream;

  public InputStream getInputStream() {
    return inputStream;
  }

  private static final String DEFAULT_QUERY_ENCODING = "utf-8";

  private static void parseQuery(Map<String, String> parameters, String rawQuery, String separator)
  throws IncorrectHttpRequestException, UnsupportedEncodingException {
    if (rawQuery == null)
      return;
    for (String keyValuePair : rawQuery.split(separator)) {
      String[] spl = keyValuePair.split("=");
      if (spl.length != 2)
        throw new IncorrectHttpRequestException("The query part of the request is incorrect");
      parameters.put(URLDecoder.decode(spl[0], DEFAULT_QUERY_ENCODING), URLDecoder.decode(spl[1], DEFAULT_QUERY_ENCODING));
    }
  }

  private final Map<String, String> headers = new HashMap<String, String>();

  private Map<String, String> unmodifiableHeaders = Collections.unmodifiableMap(headers);

  public Map<String, String> getHeaders() {
    return unmodifiableHeaders;
  }
}
